import threading
import yaml
import json
import datetime
import pathlib
import time
import shutil
import subprocess
import os
import sys
import re
import requests
import psutil
import ctypes
import typing
import configparser
import zipfile
import sqlite3
import random
import logging
import cv2
import base64
import io
import PIL.Image
from PIL import Image

from selenium.webdriver.edge.webdriver import WebDriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.edge.service import Service
from selenium.webdriver.edge.options import Options
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.common.exceptions import *

# ==============================
# 子类定义
# ==============================

class ElementFinder:
    """元素查找模块"""
    
    def __init__(self, main_class: 'Custom_WebDriver'):
        self.main: 'Custom_WebDriver' = main_class
        self.driver: WebDriver = main_class.driver
    
    def by_xpath(self, xpathvalue: str) -> 'Custom_WebDriver':
        """通过 XPath 查找元素"""
        try:
            self.main._element = self.driver.find_element(by=By.XPATH, value=xpathvalue)
            return self.main
        except NoSuchElementException:
            print(f"提示 : 无法找到目标元素!")
            return self.main
    
    def by_near_text(self, text: str, element_tag: str = "input", element_type: str = "file", 
                    text_tags: typing.Optional[list] = None, max_depth: int = 5) -> 'Custom_WebDriver':
        """根据附近文本查找元素"""
        if text_tags is None:
            text_tags = ['span', 'label', 'div']
        
        tag_selector = " or ".join([f"self::{tag}" for tag in text_tags])
        text_xpath = f"//*[({tag_selector}) and contains(text(), '{text}')]"
        
        try:
            text_elements = self.driver.find_elements(By.XPATH, text_xpath)
            
            for text_element in text_elements:
                target_element = self._find_element_near_text_element(
                    text_element, element_tag, element_type, max_depth)
                if target_element:
                    self.main._element = target_element
                    return self.main
                    
        except Exception as e:
            print(f"根据附近文本查找元素出错: {e}")
        
        print(f"提示: 未找到文本 '{text}' 附近的 {element_tag}[type='{element_type}'] 元素")
        return self.main

    def _find_element_near_text_element(self, text_element: WebElement, element_tag: str, 
                                      element_type: str, max_depth: int) -> typing.Optional[WebElement]:
        """在文本元素附近查找目标元素"""
        current_element = text_element
        
        for depth in range(max_depth):
            try:
                patterns = [
                    f"./following-sibling::*//{element_tag}[@type='{element_type}']",
                    f"./preceding-sibling::*//{element_tag}[@type='{element_type}']", 
                    f"./parent::*//{element_tag}[@type='{element_type}']",
                    f"./ancestor::*[position()={depth+1}]//{element_tag}[@type='{element_type}']"
                ]
                
                for pattern in patterns:
                    try:
                        element = current_element.find_element(By.XPATH, pattern)
                        if element:
                            return element
                    except NoSuchElementException:
                        continue
            except Exception:
                pass
            
            try:
                current_element = current_element.find_element(By.XPATH, "./parent::*")
            except NoSuchElementException:
                break
        
        return None

    def with_condition(self, xpath: str, condition_type: typing.Optional[str] = None, 
                      expected_value: typing.Optional[str] = None, timeout: int = 10) -> 'Custom_WebDriver':
        """根据条件查找元素"""
        start_time = datetime.datetime.now()
        
        while datetime.datetime.now() - start_time < datetime.timedelta(seconds=timeout):
            try:
                element = self.driver.find_element(By.XPATH, xpath)
                
                if not condition_type:
                    self.main._element = element
                    return self.main
                
                if self._check_element_condition(element, condition_type, expected_value):
                    self.main._element = element
                    return self.main
                    
            except NoSuchElementException:
                pass
            
            time.sleep(0.5)
        
        print(f"提示: 未找到满足条件的元素: {xpath}")
        return self.main

    def _check_element_condition(self, element: WebElement, condition_type: typing.Optional[str], 
                               expected_value: typing.Optional[str]) -> bool:
        """检查元素条件"""
        try:
            if condition_type == 'value_null':
                return element.get_attribute("value") == ""
            elif condition_type == 'value_not_null':
                return element.get_attribute("value") != ""
            elif condition_type == 'value_equals':
                return element.get_attribute("value") == expected_value
            elif condition_type == 'value_contains':
                return expected_value in element.get_attribute("value")
            elif condition_type == 'text_null':
                return element.text == ""
            elif condition_type == 'text_not_null':
                return element.text != ""
            elif condition_type == 'text_equals':
                return element.text == expected_value
            elif condition_type == 'text_contains':
                return expected_value in element.text
            elif condition_type == 'enabled':
                return element.is_enabled()
            elif condition_type == 'disabled':
                return not element.is_enabled()
            elif condition_type == 'visible':
                return element.is_displayed()
            return False
        except Exception:
            return False

    def upload_input_by_text(self, text: str, max_depth: int = 5) -> 'Custom_WebDriver':
        """通过附近文本查找文件上传输入框"""
        return self.by_near_text(
            text=text,
            element_tag="input",
            element_type="file",
            text_tags=['span', 'label', 'div', 'p', 'button', 'a'],
            max_depth=max_depth
        )


class ElementAction:
    """元素操作模块"""
    
    def __init__(self, main_class: 'Custom_WebDriver'):
        self.main: 'Custom_WebDriver' = main_class
        self.driver: WebDriver = main_class.driver
    
    def click(self) -> 'Custom_WebDriver':
        """点击元素"""
        if hasattr(self.main, '_element') and self.main._element:
            self.main._element.click()
        return self.main
    
    def script_click(self) -> 'Custom_WebDriver':
        """JavaScript 点击"""
        if hasattr(self.main, '_element') and self.main._element:
            self.driver.execute_script("arguments[0].click();", self.main._element)
        return self.main
    
    def safe_click(self) -> 'Custom_WebDriver':
        """安全点击"""
        if hasattr(self.main, '_element') and self.main._element:
            _element = self.main._element
            try:
                self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", _element)
                try:
                    _element.click()
                except (ElementClickInterceptedException, ElementNotInteractableException):
                    self.driver.execute_script("arguments[0].click();", _element)
            except:
                pass
        return self.main
    
    def send_keys(self, keys: str) -> 'Custom_WebDriver':
        """发送按键"""
        if hasattr(self.main, '_element') and self.main._element:
            self.main._element.send_keys(keys)
        return self.main
    
    def clean_input(self, attribute: str = "value") -> 'Custom_WebDriver':
        """清空输入框"""
        if hasattr(self.main, '_element') and self.main._element:
            _element = self.main._element
            _startTime = datetime.datetime.now()
            _seconds = 5
            
            try:
                self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", _element)
                try:
                    _element.click()
                except (ElementClickInterceptedException, ElementNotInteractableException):
                    self.driver.execute_script("arguments[0].click();", _element)
            except:
                pass
            
            if _element.get_attribute(attribute) != "":
                while _startTime + datetime.timedelta(seconds=_seconds) > datetime.datetime.now():
                    _element.send_keys(Keys.BACKSPACE * 20)
                    if _element.get_attribute(attribute) == "":
                        break
        return self.main
    
    def safe_send_keys(self, value: str, attribute: str = "value") -> 'Custom_WebDriver':
        """安全发送按键"""
        if hasattr(self.main, '_element') and self.main._element:
            _element = self.main._element
            _startTime = datetime.datetime.now()
            _seconds = 5
            _elementAttribute_value = value
            
            try:
                self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", _element)
                try:
                    _element.click()
                except (ElementClickInterceptedException, ElementNotInteractableException):
                    self.driver.execute_script("arguments[0].click();", _element)
            except:
                pass
            
            if _element.get_attribute(attribute) != _elementAttribute_value:
                while _startTime + datetime.timedelta(seconds=_seconds) > datetime.datetime.now():
                    _element.send_keys(Keys.BACKSPACE * 20)
                    if _element.get_attribute(attribute) == "":
                        break
                _element.send_keys(_elementAttribute_value)
        return self.main


class FileUploader:
    """文件上传模块"""
    
    def __init__(self, main_class: 'Custom_WebDriver'):
        self.main: 'Custom_WebDriver' = main_class
    
    def single_file(self, file_path: str) -> 'Custom_WebDriver':
        """上传单个文件"""
        if hasattr(self.main, '_element') and self.main._element:
            try:
                if os.path.exists(file_path):
                    self.main._element.send_keys(file_path)
                    print(f"文件上传成功: {file_path}")
                else:
                    print(f"文件不存在: {file_path}")
            except Exception as e:
                print(f"文件上传失败: {e}")
        else:
            print("提示: 未找到上传元素")
        return self.main
    
    def directory(self, directory_path: str) -> 'Custom_WebDriver':
        """上传文件夹"""
        picture_name_list = []
        for item in pathlib.Path(directory_path).rglob('*'):
            if item.is_file():
                picture_image_path = str(item)
                picture_name_list.append(picture_image_path)
        
        picture_list_str = ""
        for index, picture_name in enumerate(picture_name_list):
            picture_path = str(pathlib.Path(picture_name).absolute().resolve())
            if index >= 5:
                continue
            if index > 0:
                picture_list_str += "\n"
            picture_list_str += picture_path
        
        return self.single_file(picture_list_str)
    
    def file_list(self, picture_name_list: list) -> 'Custom_WebDriver':
        """上传文件列表"""
        picture_list_str = ""
        for index, picture_name in enumerate(picture_name_list):
            picture_path = str(pathlib.Path(picture_name).absolute().resolve())
            if index >= 5:
                continue
            if index > 0:
                picture_list_str += "\n"
            picture_list_str += picture_path
        
        return self.single_file(picture_list_str)


class ElementWaiter:
    """等待策略模块"""
    
    def __init__(self, main_class: 'Custom_WebDriver'):
        self.main: 'Custom_WebDriver' = main_class
        self.driver: WebDriver = main_class.driver
    
    def implicitly(self, xpathvalue: str, timeout: float = 10) -> 'Custom_WebDriver':
        """隐式等待查找元素"""
        start_time = time.time()
        while time.time() - start_time < timeout:
            try:
                self.main._element = self.driver.find_element(by=By.XPATH, value=xpathvalue)
                return self.main
            except NoSuchElementException:
                time.sleep(0.5)
        print(f"提示 : 无法找到目标元素!")
        return self.main
    
    def check_exists(self, xpathvalue: str, wait_time: int = 2) -> typing.Optional['Custom_WebDriver']:
        """验证元素是否存在"""
        _startTime = datetime.datetime.now()
        _seconds = wait_time
        _elementDetectResult = False
        
        while _startTime + datetime.timedelta(seconds=_seconds) > datetime.datetime.now():
            try:
                _element = self.driver.find_element(by=By.XPATH, value=xpathvalue)
                _elementDetectResult = True
                break
            except NoSuchElementException:
                _elementDetectResult = False
                continue
        
        return self.main if _elementDetectResult else None


class ElementScroller:
    """滚动操作模块"""
    
    def __init__(self, main_class: 'Custom_WebDriver'):
        self.main: 'Custom_WebDriver' = main_class
        self.driver: WebDriver = main_class.driver
    
    def to_center(self) -> 'Custom_WebDriver':
        """滚动到元素中心"""
        if hasattr(self.main, '_element') and self.main._element:
            self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", self.main._element)
        return self.main
    
    def to_left_top(self, offset_x: int = 0, offset_y: int = 0) -> 'Custom_WebDriver':
        """滚动到元素左上角"""
        if hasattr(self.main, '_element') and self.main._element:
            Element_Local_X = self.main._element.location['x']
            Element_Local_Y = self.main._element.location['y']
            self.driver.execute_script(f"window.scrollTo({Element_Local_X - offset_x},{Element_Local_Y - offset_y});")
        return self.main


class ElementHover:
    """悬停操作模块"""
    
    def __init__(self, main_class: 'Custom_WebDriver'):
        self.main: 'Custom_WebDriver' = main_class
        self.driver: WebDriver = main_class.driver
    
    def simple(self) -> 'Custom_WebDriver':
        """简单悬停"""
        if hasattr(self.main, '_element') and self.main._element:
            ActionChains(driver=self.driver).move_to_element(self.main._element).perform()
        return self.main
    
    def script_hover(self, duration: float = 0.5, offset_x: int = 0, offset_y: int = 0) -> 'Custom_WebDriver':
        """JavaScript 悬停"""
        if hasattr(self.main, '_element') and self.main._element:
            js_script = """
            function simulateHover(element, duration, offsetX, offsetY) {
                const rect = element.getBoundingClientRect();
                const centerX = rect.left + rect.width / 2 + offsetX;
                const centerY = rect.top + rect.height / 2 + offsetY;
                
                const mouseMoveEvent = new MouseEvent('mousemove', {
                    view: window,
                    bubbles: true,
                    cancelable: true,
                    clientX: centerX,
                    clientY: centerY
                });
                
                const mouseOverEvent = new MouseEvent('mouseover', {
                    view: window,
                    bubbles: true,
                    cancelable: true,
                    clientX: centerX,
                    clientY: centerY
                });
                
                const mouseEnterEvent = new MouseEvent('mouseenter', {
                    view: window,
                    bubbles: true,
                    cancelable: true,
                    clientX: centerX,
                    clientY: centerY
                });
                
                document.dispatchEvent(mouseMoveEvent);
                element.dispatchEvent(mouseOverEvent);
                element.dispatchEvent(mouseEnterEvent);
                
                if (duration > 0) {
                    return new Promise(resolve => {
                        setTimeout(resolve, duration * 1000);
                    });
                }
            }
            
            const promise = simulateHover(arguments[0], arguments[1], arguments[2], arguments[3]);
            
            if (promise) {
                const done = arguments[arguments.length - 1];
                promise.then(done);
            }
            """
            
            self.driver.execute_async_script(js_script, self.main._element, duration, offset_x, offset_y)
        return self.main


# ==============================
# 主类定义
# ==============================

class Custom_WebDriver:
    """单例类，用于管理 Edge 浏览器的 WebDriver 实例。"""

    _instance: typing.Optional['Custom_WebDriver'] = None

    def __new__(cls, existing_driver: typing.Optional[WebDriver] = None) -> 'Custom_WebDriver':
        if cls._instance is None:
            cls._instance = super(Custom_WebDriver, cls).__new__(cls)
            if existing_driver:
                cls._instance.driver = existing_driver
            else:
                cls._instance.options = Options()
                cls._instance.service = Service()
                cls._instance.options.add_experimental_option("debuggerAddress", "127.0.0.1:9652")
                cls._instance.driver = WebDriver(options=cls._instance.options)
            cls._instance.actions = ActionChains(cls._instance.driver)
            cls._instance.wait = WebDriverWait(driver=cls._instance.driver, timeout=10)
            
            # 初始化模块引用
            cls._instance._finder = None
            cls._instance._action = None
            cls._instance._uploader = None
            cls._instance._waiter = None
            cls._instance._scroller = None
            cls._instance._hover = None
            
        return cls._instance

    def __init__(self, existing_driver: typing.Optional[WebDriver] = None) -> None:
        if existing_driver:
            self.driver: WebDriver = existing_driver
        else:
            self.options: Options = Options()
            self.service: Service = Service()
            self.options.add_experimental_option("debuggerAddress", "127.0.0.1:9652")
            self.driver: WebDriver = WebDriver(options=self.options)

    # ==============================
    # 模块属性接口
    # ==============================
    
    @property
    def finder(self) -> ElementFinder:
        """元素查找模块"""
        if self._finder is None:
            self._finder = ElementFinder(self)
        return self._finder
    
    @property
    def action(self) -> ElementAction:
        """元素操作模块"""
        if self._action is None:
            self._action = ElementAction(self)
        return self._action
    
    @property
    def uploader(self) -> FileUploader:
        """文件上传模块"""
        if self._uploader is None:
            self._uploader = FileUploader(self)
        return self._uploader
    
    @property
    def waiter(self) -> ElementWaiter:
        """等待策略模块"""
        if self._waiter is None:
            self._waiter = ElementWaiter(self)
        return self._waiter
    
    @property
    def scroller(self) -> ElementScroller:
        """滚动操作模块"""
        if self._scroller is None:
            self._scroller = ElementScroller(self)
        return self._scroller
    
    @property
    def hover(self) -> ElementHover:
        """悬停操作模块"""
        if self._hover is None:
            self._hover = ElementHover(self)
        return self._hover

    # ==============================
    # 基础方法
    # ==============================
    
    def Goto_CurrentActivateUrl(self) -> typing.Optional['Custom_WebDriver']:
        """切换到当前激活的页面"""
        debug_json_data = requests.get(
            url="http://127.0.0.1:9652/json", timeout=5).json()

        if not isinstance(debug_json_data, list):
            print("接口返回数据格式不正确（非列表或为空）")
            return None

        filter_json_data = list(
                                    filter(lambda item: (
                                        item['type'] == "page" and
                                        not item['title'].lower().startswith("devtools") and
                                        not item['title'].lower().startswith("offscreen") and
                                        not item['title'].lower().startswith("新标签页") and
                                        not item['url'].lower().startswith("edge://newtab/") and
                                        not item['url'].lower().startswith("chrome-extension") and
                                        not item['url'].lower().startswith("chrome-devtools")
                                    ), debug_json_data)
                                )
        
        if filter_json_data:
            current_activate_handle = filter_json_data[0]['id']
            self.driver.switch_to.window(window_name=filter_json_data[0]['id'])
        return self

    def delay(self, times: float) -> 'Custom_WebDriver':
        """延迟等待"""
        time.sleep(times)
        return self

    def set_implicitly_wait(self, seconds: float = 5) -> 'Custom_WebDriver':
        """设置隐式等待时间"""
        self.driver.implicitly_wait(seconds)
        return self

    def set_current_element(self, _element: WebElement) -> 'Custom_WebDriver':
        """设置当前操作的元素"""
        self._element = _element
        return self

    def capture(self, 
                save_path: typing.Optional[str] = None,
                return_image: bool = False,
                element_only: bool = False) -> typing.Union['Custom_WebDriver', Image.Image, None]:
        """
        截图功能
        
        Args:
            save_path: 保存路径，如果为None则不保存
            return_image: 是否返回PIL图像对象
            element_only: 是否只截取当前元素
            
        Returns:
            根据参数返回不同的值
        """
        try:
            if element_only and hasattr(self, '_element') and self._element:
                # 截取特定元素
                screenshot_data = self._element.screenshot_as_png
            else:
                # 截取整个页面
                screenshot_data = self.driver.get_screenshot_as_png()
            
            # 转换为PIL图像
            image = Image.open(io.BytesIO(screenshot_data))
            
            # 保存到文件
            if save_path:
                # 确保目录存在
                os.makedirs(os.path.dirname(save_path), exist_ok=True)
                image.save(save_path)
                print(f"截图已保存: {save_path}")
            
            # 返回图像对象
            if return_image:
                return image
            
            return self
            
        except Exception as e:
            print(f"截图失败: {e}")
            return None if return_image else self

    def capture_element_screenshot(self, 
                                  save_path: typing.Optional[str] = None,
                                  return_image: bool = False) -> typing.Union['Custom_WebDriver', Image.Image, None]:
        """
        截取当前元素的截图
        
        Args:
            save_path: 保存路径
            return_image: 是否返回PIL图像对象
            
        Returns:
            根据参数返回不同的值
        """
        if not hasattr(self, '_element') or not self._element:
            print("提示: 未设置当前元素")
            return None if return_image else self
            
        return self.capture(save_path=save_path, return_image=return_image, element_only=True)

    def capture_full_page(self, 
                         save_path: typing.Optional[str] = None,
                         return_image: bool = False) -> typing.Union['Custom_WebDriver', Image.Image, None]:
        """
        截取完整页面截图（包括滚动部分）
        
        Args:
            save_path: 保存路径
            return_image: 是否返回PIL图像对象
            
        Returns:
            根据参数返回不同的值
        """
        try:
            # 获取页面总高度
            total_height = self.driver.execute_script("return document.body.scrollHeight")
            # 获取视口高度
            viewport_height = self.driver.execute_script("return window.innerHeight")
            
            # 设置窗口大小为页面总高度
            original_size = self.driver.get_window_size()
            self.driver.set_window_size(original_size['width'], total_height)
            
            # 截取完整页面
            screenshot_data = self.driver.get_screenshot_as_png()
            image = Image.open(io.BytesIO(screenshot_data))
            
            # 恢复原始窗口大小
            self.driver.set_window_size(original_size['width'], original_size['height'])
            
            # 保存到文件
            if save_path:
                os.makedirs(os.path.dirname(save_path), exist_ok=True)
                image.save(save_path)
                print(f"完整页面截图已保存: {save_path}")
            
            # 返回图像对象
            if return_image:
                return image
            
            return self
            
        except Exception as e:
            print(f"完整页面截图失败: {e}")
            return None if return_image else self

    def prompt(self, times: float = 0.2) -> 'Custom_WebDriver':
        """高亮元素"""
        if hasattr(self, '_element') and self._element:
            src_style_value = self._element.get_attribute("style")
            new_style_value = f"{src_style_value}; border: 5px solid red;" if src_style_value else "border: 5px solid red;"
            self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", self._element, new_style_value)
            time.sleep(times)
            self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", self._element, src_style_value)
        return self


# ==============================
# 使用示例
# ==============================

if __name__ == "__main__":
    print("提示 : 模块测试")
    driver = Custom_WebDriver()
    driver.Goto_CurrentActivateUrl()
    
    # 截图使用示例
    # 1. 简单截图并保存
    driver.capture("screenshot.png")
    driver
    # 2. 截取当前元素
    # driver.finder.by_xpath("//input").capture_element_screenshot("element.png")
    
    # 3. 截取完整页面
    # driver.capture_full_page("full_page.png")
    
    # 4. 获取图像对象进行处理
    # image = driver.capture(return_image=True)
    # if image:
    #     image.show()
    
    # driver.Goto_CurrentActivateUrl().finder.by_xpath("//input[@class='nav-search-input']").prompt().action.clean_input().prompt().action.safe_send_keys("test_user").prompt().delay(1)